namespace ImapX
{
    using EmailParser;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;
    using Parsing.DataItems;

    public class Message
    {
        private List<Attachment> _attachments = new List<Attachment>();
        private string _bcc;
        private List<MessageContent> _bodyParts = new List<MessageContent>();
        private string _cc;
        internal Imap _client;
        private string _contentTransferEncoding;
        private string _contentType;
        private DateTime _date;
        private EmailParser _emailParser;
        private Flags _flags = new Flags();
        private List<MailAddress> _from = new List<MailAddress>();
        private Dictionary<string, string> _headers = new Dictionary<string, string>();
        private MessageContent _htmlBody = new MessageContent();
        private string _messageId;
        private string _mimeVersion;
        private int _msgUID;
        private string _organization;
        private string _priority;
        private string _received;
        private string _references;
        private string _replyTo;
        private string _subject;
        private MessageContent _textBody = new MessageContent();
        private List<MailAddress> _to = new List<MailAddress>();
        private string _xMailer;
        internal string _originalDateString;

        public Message(Imap client = null, int uid = -1, Folder folder = null)
        {
            Folder = folder;
            _client = client;
            _msgUID = uid;
        }

        public Folder Folder { get; internal set; }

        public string GetAsString()
        {
            return this.getEml().ToString();
        }

        private StringBuilder getEml()
        {
            StringBuilder builder = new StringBuilder();
            if (((this._emailParser != null) && (this._emailParser._emailItems != null)) && (this._emailParser._emailItems.Length > 0))
            {
                for (int i = 1; i <= (this._emailParser._emailItems.Length - 2); i++)
                {
                    if ((i == (this._emailParser._emailItems.Length - 2)) && (this._emailParser._emailItems[i].Length > 0))
                    {
                        builder.AppendFormat("{0}{1}", this._emailParser._emailItems[i].TrimEnd(new char[] { ')' }), "\r\n");
                    }
                    else
                    {
                        builder.AppendFormat("{0}{1}", this._emailParser._emailItems[i], "\r\n");
                    }
                }
                return builder;
            }
            builder.Append(this.messageBuilder());
            return builder;
        }

        private void getFlags()
        {
            _client.FetchDataItems(this._msgUID, Flags);
        }

        public override int GetHashCode()
        {
            return this.MessageId.GetHashCode();
        }

        public override string ToString()
        {
            return "uid:" + _msgUID + " folder: " + Folder.FolderPath + ", id:" + _messageId + ", subject:" + _subject + ", date:" + _date.ToString() + ", size: " + _size.ToString();
        }

        static private string GetDataFromResult(string result)
        {
            Regex regex2 = new Regex(@"^[^\n]*\n(?<data>(.*\n)*)\)\r\nIMAP");
            Match match2 = regex2.Match(result);
            if (!match2.Success) throw new ImapException("Unable to parse message data from: " + result);
            return match2.Groups["data"].Value;
        }

        private void getMessage(string path, bool processBody)
        {
            Folder.Select();

            string result = _client.UidFetch(_msgUID, path);

            Match match = Regex.Match(result, @"(FLAGS\s+\(((?<flag>[\\\w]+)\s*)*\)\s+)?(?<dataitem>[\w\[\]]*)\s+\{(?<dataLength>\d+)\}\r\n(?<data>(.*\n)*)$");

            if (!match.Success) throw new ImapException("Parsing fetched data failed.");

            string data = match.Groups["data"].Value;

            this._emailParser = new EmailParser(data.Split(new string[] { "\r\n" }, StringSplitOptions.None));

            this._emailParser.InitializeIndexes();
            this._emailParser.ParseHeaders();

            foreach (KeyValuePair<string, string> pair in this._emailParser._headersCollection)
            {
                string key = pair.Key.ToLower().Trim();

                if (key.Equals("to"))
                {
                    this._to = ParseHelper.AddressCollection(pair.Value);
                }
                else
                {
                    if (key.Equals("from"))
                    {
                        this._from = ParseHelper.AddressCollection(pair.Value);
                        continue;
                    }
                    if (key.Equals("date"))
                    {
                        string s = pair.Value.Trim();
                        _originalDateString = s;
                        s = s.Replace("UT", "GMT");
                        s = s.Replace("EST", "-0500");
                        s = s.Replace("EDT", "-0400");
                        s = s.Replace("CST", "-0600");
                        s = s.Replace("CDT", "-0500");
                        s = s.Replace("MST", "-0700");
                        s = s.Replace("MDT", "-0600");
                        s = s.Replace("PST", "-0800");
                        s = s.Replace("PDT", "-0700");
                        Regex regex = new Regex(@"\d?\d\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d\d\d\d\s+\d?\d:\d\d(:\d?\d)?(\s+([\+-]?\d\d\d\d|GMT))?");
                        Match dateMatch = regex.Match(s);
                        if (!dateMatch.Success)
                        {
                            throw new Exception();
                        }
                        this._date = DateTime.Parse(dateMatch.Value);
                        continue;
                    }
                    if ((((!ParseHelper.MessageProperty(pair.Key, pair.Value, "content-transfer-encoding", ref this._contentTransferEncoding) && !ParseHelper.MessageProperty(pair.Key, pair.Value, "content-type", ref this._contentType)) && (!ParseHelper.MessageProperty(pair.Key, pair.Value, "message-id", ref this._messageId) && !ParseHelper.MessageProperty(pair.Key, pair.Value, "mime-version", ref this._mimeVersion))) && ((!ParseHelper.MessageProperty(pair.Key, pair.Value, "organization", ref this._organization) && !ParseHelper.MessageProperty(pair.Key, pair.Value, "priority", ref this._priority)) && (!ParseHelper.MessageProperty(pair.Key, pair.Value, "received", ref this._received) && !ParseHelper.MessageProperty(pair.Key, pair.Value, "references", ref this._references)))) && ((!ParseHelper.MessageProperty(pair.Key, pair.Value, "reply-to", ref this._replyTo) && !ParseHelper.MessageProperty(pair.Key, pair.Value, "x-mailer", ref this._xMailer)) && (!ParseHelper.MessageProperty(pair.Key, pair.Value, "cc", ref this._cc) && !ParseHelper.MessageProperty(pair.Key, pair.Value, "bcc", ref this._bcc))))
                    {
                        ParseHelper.MessageProperty(pair.Key, pair.Value, "subject", ref this._subject);
                    }
                }
            }
            this._headers = this._emailParser._headersCollection;
            if (processBody)
            {
                this._emailParser.ParseBody();
                foreach (BodyPart part in this._emailParser._parts)
                {
                    MessageContent item = new MessageContent();
                    foreach (KeyValuePair<string, string> pair2 in part.Headers)
                    {
                        if (pair2.Key.ToLower().Equals("content-type"))
                        {
                            item.ContentType = pair2.Value;
                        }
                        else
                        {
                            if (pair2.Key.ToLower().Equals("content-disposition"))
                            {
                                item.ContentDisposition = pair2.Value;
                                if (pair2.Value.ToLower().Contains("filename="))
                                {
                                    string str3 = pair2.Value.Split(new string[] { "filename=" }, StringSplitOptions.None)[1].Trim(new char[] { '"' });
                                    item.ContentFilename = str3;
                                    continue;
                                }
                            }
                            if (pair2.Key.ToLower().Equals("content-description"))
                            {
                                item.ContentDescription = pair2.Value;
                                continue;
                            }
                            if (pair2.Key.ToLower().Equals("content-transfer-encoding"))
                            {
                                item.ContentTransferEncoding = pair2.Value;
                            }
                        }
                    }
                    if (part.Boundary == null)
                    {
                        item.ContentType = this._contentType;
                    }
                    item.ContentStream = this._emailParser.GetPart(part);
                    item.PartHeaders = part.Headers;
                    item.BoundaryName = part.Boundary;
                    this._bodyParts.Add(item);
                }
            }
        }

        public string messageBuilder()
        {
            StringBuilder builder = new StringBuilder();
            DateTime now = DateTime.Now;
            builder.AppendFormat("Date: {0}{1}", this._date.ToString("dd-MM-yyyy hh:mm:ss", CultureInfo.CreateSpecificCulture("en-US")), "\r\n");
            if (this._from.Count > 0)
            {
                builder.Append("From: ");
                foreach (MailAddress address in this._from)
                {
                    builder.AppendFormat("{0}, ", address.ToString());
                }
                builder.Remove(builder.Length - 2, 2);
                builder.Append("\r\n");
            }
            if (this._to.Count > 0)
            {
                builder.Append("To: ");
                foreach (MailAddress address2 in this._to)
                {
                    builder.AppendFormat("{0}, ", address2.ToString());
                }
                builder.Remove(builder.Length - 2, 1);
                builder.Append("\r\n");
            }
            if (!string.IsNullOrEmpty(this._subject))
            {
                builder.AppendFormat("Subject: {0}{1}", this._subject, "\r\n");
            }
            if (string.IsNullOrEmpty(this._messageId))
            {
                builder.AppendFormat("Message-Id: {0}@ImapX{1}", Guid.NewGuid(), "\r\n");
            }
            else
            {
                builder.AppendFormat("Message-Id: {0}{1}", this._messageId, "\r\n");
            }
            if (!string.IsNullOrEmpty(this.XMailer))
            {
                builder.AppendFormat("X-Mailer: {0}{1}", this._xMailer, "\r\n");
            }
            if (!string.IsNullOrEmpty(this._contentTransferEncoding))
            {
                builder.AppendFormat("Content-Transfer-Encoding: {0}{1}", this.ContentTransferEncoding, "\r\n");
            }
            if (string.IsNullOrEmpty(this.MimeVersion))
            {
                builder.AppendFormat("MIME-Version: 1.0{0}", "\r\n");
            }
            else
            {
                builder.AppendFormat("MIME-Version: {0}{1}", this._mimeVersion, "\r\n");
            }
            if ((this._textBody == null) && (this._attachments.Count <= 0))
            {
                builder.AppendFormat("Content-Type: text/html; charset=utf-8{0}", "\r\n");
                builder.AppendFormat("Content-Transfer-Encoding: base64{0}", "\r\n");
                builder.Append("\r\n");
                if ((this._htmlBody != null) && (this._htmlBody.TextData != null))
                {
                    builder.AppendFormat("{0}{1}", Convert.ToBase64String(Encoding.UTF8.GetBytes(this._htmlBody.TextData)), Environment.NewLine);
                }
                else
                {
                    builder.AppendFormat(Environment.NewLine, new object[0]);
                }
            }
            else
            {
                builder.Append("Content-Type: multipart/mixed;");
                builder.AppendFormat("boundary=\"part000\"{0}", "\r\n");
                builder.AppendFormat("--part000{0}", "\r\n");
                builder.AppendFormat("Content-Type: text/plain; charset=utf-8{0}", "\r\n");
                builder.AppendFormat("Content-Transfer-Encoding: base64{0}", "\r\n");
                builder.Append("\r\n");
                if ((this._textBody != null) && (this._textBody.TextData != null))
                {
                    builder.AppendFormat("{0}{1}", Convert.ToBase64String(Encoding.UTF8.GetBytes(this._textBody.TextData)), Environment.NewLine);
                }
                else
                {
                    builder.AppendFormat(Environment.NewLine, new object[0]);
                }
                builder.AppendFormat("--part000{0}", "\r\n");
                builder.AppendFormat("Content-Type: text/html; charset=utf-8{0}", "\r\n");
                builder.AppendFormat("Content-Transfer-Encoding: base64{0}", "\r\n");
                builder.Append("\r\n");
                if ((this._htmlBody != null) && (this._htmlBody.TextData != null))
                {
                    builder.AppendFormat("{0}{1}", Convert.ToBase64String(Encoding.UTF8.GetBytes(this._htmlBody.TextData)), Environment.NewLine);
                }
                else
                {
                    builder.AppendFormat(Environment.NewLine, new object[0]);
                }
                foreach (Attachment attachment in this._attachments)
                {
                    builder.AppendFormat("--part000{0}", Environment.NewLine);
                    builder.AppendFormat("Content-Type: {0}; name=\"{1}\"{2}", attachment.FileType, attachment.FileName.Substring(attachment.FileName.IndexOf('/')).Trim(new char[] { '/' }), Environment.NewLine);
                    if (string.IsNullOrEmpty(attachment.FileEncoding))
                    {
                        builder.AppendFormat("Content-Transfer-Encoding: base64{0}", Environment.NewLine);
                    }
                    else
                    {
                        builder.AppendFormat("Content-Transfer-Encoding: {0}{1}", attachment.FileEncoding, Environment.NewLine);
                    }
                    builder.AppendFormat("Content-Disposition: attachment; filename=\"{0}\"{1}", attachment.FileName.Substring(attachment.FileName.LastIndexOf('/')).Trim(new char[] { '/' }), Environment.NewLine);
                    builder.Append(Environment.NewLine);
                    builder.Append(attachment.GetStream());
                    builder.Append(Environment.NewLine);
                }
                builder.AppendFormat("--part000--{0}", Environment.NewLine);
            }
            return builder.ToString();
        }

        public bool Process()
        {
            this.getFlags();
            ProcessBody();
            return true;
        }

        public void ProcessBody()
        {
            getMessage("BODY.PEEK[]", true);
            foreach (MessageContent content in this._bodyParts)
            {
                if ((content.ContentType != null) && content.ContentType.ToLower().Contains("text/plain"))
                {
                    this._textBody = content;
                    this._textBody.TextData = this._textBody.ContentStream;
                }
            }
            foreach (MessageContent content2 in this._bodyParts)
            {
                if ((content2.ContentType != null) && content2.ContentType.ToLower().Contains("text/html"))
                {
                    this._htmlBody = content2;
                    this._htmlBody.TextData = this._htmlBody.ContentStream;
                }
            }
            foreach (MessageContent content3 in this._bodyParts)
            {
                if ((content3.ContentDisposition != null)
                    && content3.ContentDisposition.ToLower().Contains("attachment")
                    && content3.ContentTransferEncoding.ToLower().Contains("base64"))
                {
                    Attachment item = new Attachment();
                    item.FileName = content3.ContentFilename;
                    item.FileType = content3.ContentType;
                    item.FileEncoding = content3.ContentTransferEncoding;
                    item.FileData = Convert.FromBase64String(content3.ContentStream);
                    this._attachments.Add(item);
                }
            }
        }

        public void ProcessFlags()
        {
            Folder.Select();
            this.getFlags();
        }

        public void ProcessHeader()
        {
            Folder.Select();
            this.getMessage("BODY.PEEK[HEADER]", false);
        }

        const string EnvelopeAddress = "";

        public void ProcessEnvelope()
        {
            Folder.Select();
            _client.FetchDataItems(_msgUID, Envelope);
        }
        
        public void SaveAsEmlToFile(string path, string filename)
        {
            if (!Directory.Exists(path))
            {
                throw new ImapException("Directory not Exists");
            }
            if (filename == null)
            {
                filename = Guid.NewGuid().ToString();
            }
            StringBuilder builder = this.getEml();
            using (FileStream stream = new FileStream(path + filename + ".eml", FileMode.Create, FileAccess.Write))
            {
                using (TextWriter writer = new StreamWriter(stream, Encoding.ASCII))
                {
                    writer.Write(builder.ToString());
                }
            }
        }

        static public void SetFlag(Imap client, string flags, int messageUID)
        {
            string command = string.Concat("UID STORE ", messageUID, " +FLAGS (", flags, ")\r\n");
            client.SendAndReceive(command);
        }

        public void SetFlag(string flags)
        {
            SetFlag(_client, flags, _msgUID);
            getFlags();
        }

        private Envelope _envelope;
        public Envelope Envelope
        {
            get
            {
                if (_envelope == null)
                {
                    try
                    {
                        _envelope = new Envelope();
                        ProcessEnvelope();
                    }
                    catch
                    {
                        _envelope = null;
                        throw;
                    }
                }

                return _envelope;
            }

            internal set
            {
                _envelope = value;
            }
        }

        private string _internalDate;

        public string InternalDate
        {
            get
            {
                if (_internalDate == null)
                {
                    // Cannot use the envelope INTERNALDATE for appending messages (strange stuff)
                    // Appears maybe the time offset must be normalized to +0000
                    //if (_envelope != null)
                    //{
                    //    _internalDate = _envelope.Date.Value;
                    //}
                    //else
                    {
                        InternalDate date = new InternalDate();
                        _client.FetchDataItems(_msgUID, date);
                        _internalDate = date.Value;
                    }
                }
                return _internalDate;
            }
        }

        internal byte[] _originalData;

        public byte[] OriginalData
        {
            get
            {
                if (_originalData == null)
                {
                    _originalData = _client.FetchBodyInChunks(_msgUID);
                }
                return _originalData;
            }
        }

        public void UnloadMessageBody()
        {
            _originalData = null;
        }

        public List<Attachment> Attachments
        {
            get
            {
                return this._attachments;
            }
            set
            {
                this._attachments = value;
            }
        }

        public string Bcc
        {
            get
            {
                return this._bcc;
            }
            set
            {
                this._bcc = value;
            }
        }

        public List<MessageContent> BodyParts
        {
            get
            {
                return this._bodyParts;
            }
            set
            {
                this._bodyParts = value;
            }
        }

        public string Cc
        {
            get
            {
                return this._cc;
            }
            set
            {
                this._cc = value;
            }
        }

        public string ContentTransferEncoding
        {
            get
            {
                return this._contentTransferEncoding;
            }
            set
            {
                this._contentTransferEncoding = value;
            }
        }

        public string ContentType
        {
            get
            {
                return this._contentType;
            }
            set
            {
                this._contentType = value;
            }
        }

        public DateTime Date
        {
            get
            {
                return this._date;
            }
            set
            {
                this._date = value;
            }
        }

        public Flags Flags
        {
            get
            {
                return this._flags;
            }
        }

        public List<MailAddress> From
        {
            get
            {
                return this._from;
            }
            set
            {
                this._from = value;
            }
        }

        public Dictionary<string, string> Headers
        {
            get
            {
                return this._headers;
            }
        }

        public MessageContent HtmlBody
        {
            get
            {
                return this._htmlBody;
            }
            set
            {
                this._htmlBody = value;
            }
        }

        public string MessageId
        {
            get
            {
                return Envelope.MessageID.Value;
            }
        }

        public int MessageUid
        {
            get
            {
                return this._msgUID;
            }
            set
            {
                this._msgUID = value;
            }
        }

        public string MimeVersion
        {
            get
            {
                return this._mimeVersion;
            }
            set
            {
                this._mimeVersion = value;
            }
        }

        public string Organization
        {
            get
            {
                return this._organization;
            }
            set
            {
                this._organization = value;
            }
        }

        public string Priority
        {
            get
            {
                return this._priority;
            }
            set
            {
                this._priority = value;
            }
        }

        public string Received
        {
            get
            {
                return this._received;
            }
            set
            {
                this._received = value;
            }
        }

        public string References
        {
            get
            {
                return this._references;
            }
            set
            {
                this._references = value;
            }
        }

        public string ReplyTo
        {
            get
            {
                return this._replyTo;
            }
            set
            {
                this._replyTo = value;
            }
        }

        public string Subject
        {
            get
            {
                if (_subject == null)
                {
                    _subject = Envelope.Subject.Value;
                }
                return this._subject;
            }
            set
            {
                this._subject = value;
            }
        }

        public MessageContent TextBody
        {
            get
            {
                return this._textBody;
            }
            set
            {
                this._textBody = value;
            }
        }

        public List<MailAddress> To
        {
            get
            {
                return this._to;
            }
            set
            {
                this._to = value;
            }
        }

        public string XMailer
        {
            get
            {
                return this._xMailer;
            }
            set
            {
                this._xMailer = value;
            }
        }

        private int _size = -1;
        public int Size
        {
            get
            {
                if (_size == -1)
                {
                    var size = new RFC822Size();
                    _client.FetchDataItems(_msgUID, size);
                    _size = size.Size;
                }
                return _size;
            }
        }
    }
}

